/*********************************************************************************
* Copyright 2019 Huawei Technologies Co.,Ltd.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License.  You may obtain a copy of the
* License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations under the License.
**********************************************************************************
*/
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>

#if defined __GNUC__ || defined LINUX
#include <string.h>
#include <getopt.h>
#include <strings.h>
#include <pthread.h>
#include <unistd.h>
#else
#include "getopt.h"
#endif

#include "eSDKOBS.h"
#include "obs_demo_common.h"
#include "securec.h"

#define DATA_BUF_OFFSET     100
#define ARRAY_LENGTH_1024   1024
#define ARRAY_LENGTH_2048   2048
int  showResponsePropertiesG = 1;
char locationconstraint[ARRAY_LENGTH_2048] = {0};
char ACCESS_KEY_ID[ARRAY_LENGTH_2048] = {0};
char SECRET_ACCESS_KEY[ARRAY_LENGTH_2048] = {0};
char HOST_NAME[ARRAY_LENGTH_2048] = {0};
char BUCKET_NAME[ARRAY_LENGTH_2048] = {0};
obs_canned_acl canned_acl = OBS_CANNED_ACL_BUCKET_OWNER_FULL_CONTROL;
char *CA_FILE = "./client.pem";
char UPLOAD_ID[ARRAY_LENGTH_2048] = {0};

int get_certificate_info(char *buffer, int buffer_length)
{
    int content_length = 0;
    FILE *fp = fopen(CA_FILE, "r");
    if (fp) {
        while (1) {
            int rc = fread(buffer, sizeof(char), buffer_length, fp);
            if (rc <= 0)
                break;
            content_length += rc;
        }
        fclose(fp);
    }
    return content_length;
}

FILE *write_to_file(char *localfile)
{
    FILE *outfile = 0;
    if (localfile) {
        struct stat buf;
        if (stat(localfile, &buf) == -1) {
            outfile = fopen(localfile, "wb");
        } else {
            outfile = fopen(localfile, "a");
        }

        if (!outfile) {
            fprintf(stderr, "\nERROR: Failed to open output file %s: ",
                    localfile);
            perror(0);
            return -1;
        }
    } else if (showResponsePropertiesG) {
        fprintf(stderr, "\nERROR: get -s requires a file_name parameter\n");
    } else {
        outfile = stdout;
    }
    return outfile;
}

obs_status response_properties_callback(const obs_response_properties *properties, void *callback_data)
{
    (void) callback_data;

    if (!showResponsePropertiesG)
        return OBS_STATUS_OK;

#define print_nonnull(name, field)                                 \
    do {                                                           \
        if (properties->field) {                                   \
            printf("%s: %s\n", name, properties->field);           \
        }                                                          \
    } while (0)

    print_nonnull("ETag", etag);
    print_nonnull("expiration", expiration);
    print_nonnull("website_redirect_location", website_redirect_location);
    print_nonnull("version_id", version_id);
    if (properties->last_modified > 0) {
        char timebuf[256] = {0};
        time_t t = (time_t) properties->last_modified;
        strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%SZ", gmtime(&t));
        printf("Last-Modified: %s\n", timebuf);
    }
    int i;
    for (i = 0; i < properties->meta_data_count; i++) {
        printf("x-amz-meta-%s: %s\n", properties->meta_data[i].name,
               properties->meta_data[i].value);
    }
    return OBS_STATUS_OK;
}

void put_buffer_complete_callback(obs_status status,
                                  const obs_error_details *error,
                                  void *callback_data)
{
    put_buffer_object_callback_data *data = (put_buffer_object_callback_data *)callback_data;
    data->ret_status = status;
}

int put_buffer_data_callback(int buffer_size, char *buffer, void *callback_data)
{
    put_buffer_object_callback_data *data = (put_buffer_object_callback_data *) callback_data;

    int toRead = 0;
    if (data->buffer_size) {
        toRead = ((data->buffer_size > (unsigned) buffer_size) ?
                    (unsigned) buffer_size : data->buffer_size);
        memcpy_s(buffer, buffer_size, data->put_buffer + data->cur_offset, toRead);
    }

    uint64_t originalContentLength = data->buffer_size;
    data->buffer_size -= toRead;
    data->cur_offset += toRead;
    if (data->buffer_size) {
        printf("%llu bytes remaining ", (unsigned long long)data->buffer_size);
        printf("(%d%% complete) ...\n",
               (int)(((originalContentLength - data->buffer_size) * DATA_BUF_OFFSET) / originalContentLength));
    }

    return toRead;
}

obs_status get_properties_callback(const obs_response_properties *properties, void *callback_data)
{
    if (properties->request_id)
        printf("     request id: %s \n", properties->request_id);
    if (properties->request_id2)
        printf("   request id 2: %s \n", properties->request_id2);
    if (properties->version_id)
        printf("     version id: %s \n", properties->version_id);
    if (properties->storage_class)
        printf("  storage class: %s \n", properties->storage_class);
    if (properties->bucket_location)
        printf("bucket location: %s \n", properties->bucket_location);
    if (properties->obs_version)
        printf("    obs version: %s \n", properties->obs_version);
    if (properties->restore)
        printf("        restore: %s \n", properties->restore);
    if (properties->obs_object_type)
        printf("    object type: %s \n", properties->obs_object_type);
    if (properties->obs_next_append_position)
        printf("append position: %s \n", properties->obs_next_append_position);
}

int TOKEN_BUCKET = 0;
time_t PRODUCE_TIME = 0;

uint64_t LIMIT_FLOW_MAX_SPEED = 0;
pthread_mutex_t G_MUTEXTHREADGETTOKEN; // G_MUTEXTHREADGETTOKEN

void preduce_token()
{
    if (TOKEN_BUCKET == LIMIT_FLOW_MAX_SPEED)
        return;

    int times = 0;

    if (PRODUCE_TIME == 0) {
        PRODUCE_TIME = time(0);
        times = 1;
    } else {
        time_t cur_time = time(0);
        times = (cur_time - PRODUCE_TIME);
        if (times > 0) {
            PRODUCE_TIME = cur_time;
        }
    }

    if (times > 0)
        TOKEN_BUCKET = LIMIT_FLOW_MAX_SPEED;
}

int get_token(int buffer_size)
{
    if (0 == LIMIT_FLOW_MAX_SPEED)
        return 1;
    preduce_token();

    if (TOKEN_BUCKET < buffer_size) {
        printf("has token %d  need token %d.\n", TOKEN_BUCKET, buffer_size);
        return 0;
    }

    TOKEN_BUCKET -= buffer_size;
    return 1;
}

obs_status get_object_data_callback(int buffer_size, const char *buffer,
                                    void *callback_data)
{
    pthread_mutex_lock(&G_MUTEXTHREADGETTOKEN);
    while (0 == get_token(buffer_size))
        sleep(1);

    pthread_mutex_unlock(&G_MUTEXTHREADGETTOKEN);

    get_object_callback_data *data = (get_object_callback_data *) callback_data;
    size_t wrote = fwrite(buffer, 1, buffer_size, data->outfile);
    return ((wrote < (size_t) buffer_size) ?
            OBS_STATUS_AbortedByCallback : OBS_STATUS_OK);
}

void get_object_complete_callback(obs_status status,
                                  const obs_error_details *error,
                                  void *callback_data)
{
    get_object_callback_data *data = (get_object_callback_data *) callback_data;
    data->ret_status = status;
}